package com.kexio.sms.impl;

import java.time.Duration;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.kexio.common.dto.Result;
import com.kexio.sms.config.SmsProperties;
import com.kexio.sms.entity.SmsRecord;
import com.kexio.sms.entity.VerificationCode;
import com.kexio.sms.enums.SmsType;
import com.kexio.sms.service.SmsService;
import com.kexio.sms.service.SmsVerificationService;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;

/**
 * 短信验证码服务实现
 * 
 * @author Kexio Team
 * @since 1.0.0
 */
@Service
@ConditionalOnProperty(prefix = "kexio.sms", name = "enabled", havingValue = "true")
public class SmsVerificationServiceImpl implements SmsVerificationService {
    
    private static final Logger logger = LoggerFactory.getLogger(SmsVerificationServiceImpl.class);
    
    private static final String REDIS_KEY_PREFIX = "sms:verification:";
    private static final String RATE_LIMIT_KEY_PREFIX = "sms:rate_limit:";
    
    @Autowired
    private SmsProperties smsProperties;
    
    @Autowired
    private SmsService smsService;
    
    @Autowired(required = false)
    private RedisTemplate<String, Object> redisTemplate;
    
    private final ObjectMapper objectMapper = new ObjectMapper();
    
    @Override
    public Result<String> sendVerificationCode(String phone, String type, String businessType) {
        return sendVerificationCode(phone, type, businessType, 
            smsProperties.getVerification().getDefaultLength(),
            smsProperties.getVerification().getDefaultExpireMinutes());
    }
    
    @Override
    public Result<String> sendVerificationCode(String phone, String type, String businessType, int length, int expireMinutes) {
        logger.info("发送验证码: phone={}, type={}, businessType={}, length={}, expireMinutes={}", 
                   phone, type, businessType, length, expireMinutes);
        
        try {
            // 参数验证
            if (StrUtil.isBlank(phone) || StrUtil.isBlank(type)) {
                return Result.error("手机号和验证码类型不能为空");
            }
            
            // 检查发送频率限制
            Result<Boolean> canSendResult = canSendCode(phone, type);
            if (!canSendResult.isSuccess()) {
                return Result.error(canSendResult.getMessage());
            }
            if (!canSendResult.getData()) {
                return Result.error("发送过于频繁，请稍后再试");
            }
            
            // 生成验证码
            String code = generateCode(length);
            
            // 创建验证码对象
            VerificationCode verificationCode = new VerificationCode(phone, code, type, expireMinutes);
            verificationCode.setBusinessType(businessType);
            
            // 构建短信内容
            String content = buildVerificationContent(code, expireMinutes);
            
            // 发送短信
            Result<SmsRecord> sendResult = smsService.sendSms(phone, content, SmsType.VERIFICATION.getCode());
            if (!sendResult.isSuccess()) {
                return Result.error("验证码发送失败: " + sendResult.getMessage());
            }
            
            // 保存验证码到Redis
            String redisKey = buildRedisKey(phone, type);
            if (redisTemplate != null) {
                try {
                    String verificationJson = objectMapper.writeValueAsString(verificationCode);
                    redisTemplate.opsForValue().set(redisKey, verificationJson, Duration.ofMinutes(expireMinutes));
                    
                    // 设置发送频率限制
                    setRateLimit(phone, type);
                    
                } catch (Exception e) {
                    logger.error("保存验证码到Redis失败", e);
                    return Result.error("验证码保存失败");
                }
            } else {
                logger.warn("Redis未配置，验证码无法持久化存储");
                return Result.error("验证码存储服务不可用");
            }
            
            logger.info("验证码发送成功: phone={}, type={}, recordId={}", phone, type, sendResult.getData().getId());
            return Result.success("验证码发送成功", sendResult.getData().getId());
            
        } catch (Exception e) {
            logger.error("发送验证码异常", e);
            return Result.error("发送验证码异常: " + e.getMessage());
        }
    }
    
    @Override
    public Result<Boolean> verifyCode(String phone, String code, String type) {
        return verifyCode(phone, code, type, true);
    }
    
    @Override
    public Result<Boolean> verifyCode(String phone, String code, String type, boolean consumeOnSuccess) {
        logger.info("验证验证码: phone={}, type={}, consumeOnSuccess={}", phone, type, consumeOnSuccess);
        
        try {
            // 参数验证
            if (StrUtil.isBlank(phone) || StrUtil.isBlank(code) || StrUtil.isBlank(type)) {
                return Result.error("参数不能为空");
            }
            
            // 从Redis获取验证码
            String redisKey = buildRedisKey(phone, type);
            VerificationCode verificationCode = getVerificationCodeFromRedis(redisKey);
            
            if (verificationCode == null) {
                return Result.error("验证码不存在或已过期");
            }
            
            // 检查验证码是否有效
            if (!verificationCode.isValid()) {
                if (verificationCode.isExpired()) {
                    clearCode(phone, type);
                    return Result.error("验证码已过期");
                } else if (verificationCode.getUsed()) {
                    return Result.error("验证码已使用");
                } else if (verificationCode.isMaxVerifyCountExceeded()) {
                    clearCode(phone, type);
                    return Result.error("验证失败次数过多，验证码已失效");
                }
            }
            
            // 增加验证次数
            verificationCode.incrementVerifyCount();
            
            // 验证验证码
            boolean isValid = code.equals(verificationCode.getCode());
            
            if (isValid) {
                if (consumeOnSuccess) {
                    // 标记为已使用
                    verificationCode.markAsUsed();
                }
                logger.info("验证码验证成功: phone={}, type={}", phone, type);
            } else {
                logger.warn("验证码验证失败: phone={}, type={}, verifyCount={}", 
                           phone, type, verificationCode.getVerifyCount());
            }
            
            // 更新Redis中的验证码状态
            if (redisTemplate != null) {
                try {
                    if (isValid && consumeOnSuccess) {
                        // 验证成功且消费，删除验证码
                        redisTemplate.delete(redisKey);
                    } else {
                        // 更新验证次数
                        String verificationJson = objectMapper.writeValueAsString(verificationCode);
                        long remainingExpire = redisTemplate.getExpire(redisKey, TimeUnit.SECONDS);
                        if (remainingExpire > 0) {
                            redisTemplate.opsForValue().set(redisKey, verificationJson, Duration.ofSeconds(remainingExpire));
                        }
                    }
                } catch (Exception e) {
                    logger.error("更新验证码状态失败", e);
                }
            }
            
            return Result.success(isValid ? "验证成功" : "验证码错误", isValid);
            
        } catch (Exception e) {
            logger.error("验证验证码异常", e);
            return Result.error("验证验证码异常: " + e.getMessage());
        }
    }
    
    @Override
    public VerificationCode getVerificationInfo(String phone, String type) {
        String redisKey = buildRedisKey(phone, type);
        VerificationCode code = getVerificationCodeFromRedis(redisKey);
        if (code != null) {
            // 不返回验证码内容，只返回元信息
            code.setCode("***");
        }
        return code;
    }
    
    @Override
    public Result<Boolean> canSendCode(String phone, String type) {
        try {
            if (redisTemplate == null) {
                return Result.success(true);
            }
            
            // 检查发送间隔
            String intervalKey = RATE_LIMIT_KEY_PREFIX + "interval:" + phone + ":" + type;
            if (redisTemplate.hasKey(intervalKey)) {
                long remainingTime = redisTemplate.getExpire(intervalKey, TimeUnit.SECONDS);
                if (remainingTime > 0) {
                    return Result.error("发送间隔不足，请等待" + remainingTime + "秒");
                }
            }
            
            // 检查每小时限制
            String hourKey = RATE_LIMIT_KEY_PREFIX + "hour:" + phone + ":" + LocalDateTime.now().getHour();
            Integer hourCount = (Integer) redisTemplate.opsForValue().get(hourKey);
            if (hourCount != null && hourCount >= smsProperties.getRateLimit().getMaxSendPerHourPerPhone()) {
                return Result.error("每小时发送次数已达上限");
            }
            
            // 检查每日限制
            String dayKey = RATE_LIMIT_KEY_PREFIX + "day:" + phone + ":" + LocalDateTime.now().toLocalDate();
            Integer dayCount = (Integer) redisTemplate.opsForValue().get(dayKey);
            if (dayCount != null && dayCount >= smsProperties.getRateLimit().getMaxSendPerDayPerPhone()) {
                return Result.error("每日发送次数已达上限");
            }
            
            return Result.success(true);
            
        } catch (Exception e) {
            logger.error("检查发送限制异常", e);
            return Result.success(true); // 异常时允许发送
        }
    }
    
    @Override
    public Result<Boolean> clearCode(String phone, String type) {
        try {
            String redisKey = buildRedisKey(phone, type);
            if (redisTemplate != null && redisTemplate.hasKey(redisKey)) {
                redisTemplate.delete(redisKey);
                logger.info("清除验证码: phone={}, type={}", phone, type);
                return Result.success("验证码已清除", true);
            }
            return Result.success("验证码不存在", false);
            
        } catch (Exception e) {
            logger.error("清除验证码异常", e);
            return Result.error("清除验证码异常: " + e.getMessage());
        }
    }
    
    @Override
    public long getRemainingTime(String phone, String type) {
        try {
            String redisKey = buildRedisKey(phone, type);
            if (redisTemplate != null && redisTemplate.hasKey(redisKey)) {
                return redisTemplate.getExpire(redisKey, TimeUnit.SECONDS);
            }
            return 0;
        } catch (Exception e) {
            logger.error("获取剩余时间异常", e);
            return 0;
        }
    }
    
    @Override
    public String generateCode(int length) {
        if (smsProperties.getVerification().isUseNumericCode()) {
            return RandomUtil.randomNumbers(length);
        } else if (smsProperties.getVerification().isUseAlphabeticCode()) {
            return RandomUtil.randomString(length).toUpperCase();
        } else {
            return RandomUtil.randomNumbers(length);
        }
    }
    
    @Override
    public int cleanExpiredCodes() {
        // Redis的过期机制会自动清理过期的key，这里返回0
        logger.info("Redis自动清理过期验证码");
        return 0;
    }
    
    /**
     * 构建验证码内容
     */
    private String buildVerificationContent(String code, int expireMinutes) {
        String template = smsProperties.getVerification().getTemplate();
        return template.replace("{code}", code).replace("{expire}", String.valueOf(expireMinutes));
    }
    
    /**
     * 构建Redis键
     */
    private String buildRedisKey(String phone, String type) {
        return REDIS_KEY_PREFIX + phone + ":" + type;
    }
    
    /**
     * 从Redis获取验证码
     */
    private VerificationCode getVerificationCodeFromRedis(String redisKey) {
        try {
            if (redisTemplate == null || !redisTemplate.hasKey(redisKey)) {
                return null;
            }
            
            String verificationJson = (String) redisTemplate.opsForValue().get(redisKey);
            if (StrUtil.isBlank(verificationJson)) {
                return null;
            }
            
            return objectMapper.readValue(verificationJson, VerificationCode.class);
            
        } catch (Exception e) {
            logger.error("从Redis获取验证码失败", e);
            return null;
        }
    }
    
    /**
     * 设置发送频率限制
     */
    private void setRateLimit(String phone, String type) {
        if (redisTemplate == null || !smsProperties.getRateLimit().isEnabled()) {
            return;
        }
        
        try {
            LocalDateTime now = LocalDateTime.now();
            
            // 设置发送间隔限制
            String intervalKey = RATE_LIMIT_KEY_PREFIX + "interval:" + phone + ":" + type;
            redisTemplate.opsForValue().set(intervalKey, "1", Duration.ofSeconds(smsProperties.getRateLimit().getSendIntervalSeconds()));
            
            // 设置每小时计数
            String hourKey = RATE_LIMIT_KEY_PREFIX + "hour:" + phone + ":" + now.getHour();
            redisTemplate.opsForValue().increment(hourKey);
            redisTemplate.expire(hourKey, Duration.ofHours(1));
            
            // 设置每日计数
            String dayKey = RATE_LIMIT_KEY_PREFIX + "day:" + phone + ":" + now.toLocalDate();
            redisTemplate.opsForValue().increment(dayKey);
            redisTemplate.expire(dayKey, Duration.ofDays(1));
            
        } catch (Exception e) {
            logger.error("设置发送频率限制失败", e);
        }
    }
}
